package value;

import globals.CentralStatics;
import globals.Tuple;
import value.ParetoPruner;

import java.util.ArrayList;
import java.util.LinkedList;

import lpsolve.LpSolve;
import lpsolve.LpSolveException;


public class CPruner<V extends SimpleVector<?>> implements Pruner<V> {
	private ParetoPruner<V> ppruner;

	public CPruner(){
		ppruner = new ParetoPruner<V>();
	}
	
	@Override
	public ArrayList<V> prune(ArrayList<V> in2) {
		if(in2.isEmpty()){return in2;}
		ArrayList<V> in = ppruner.prune(in2);
		LinkedList<V> all = new LinkedList<V>(in);
		//Educated guess for the size of the CCS:
		ArrayList<V> ccs = new ArrayList<V>(((int) (1.5*Math.sqrt(all.size()))));
		int dimensionality = all.peek().getValue().length;
		
		//First we add the best Payoffs for each individual weights;
		ArrayList<V> bestCorners = new ArrayList<V>(dimensionality);
		V curr = all.peek();
		for(int i=0; i<dimensionality; i++){
			bestCorners.add(curr);
		}
		for(int i=0; i<in.size(); i++){
			curr = in.get(i);
			for(int j=0; j<dimensionality; j++){
				if(curr.getValue(j)>bestCorners.get(j).getValue(j)){
					bestCorners.set(j, curr);
				}
			}
		}
		for(int i=0; i<dimensionality; i++){
			all.removeFirstOccurrence(bestCorners.get(i));
			if(!ccs.contains(bestCorners.get(i))){
				ccs.add(bestCorners.get(i));
			}
		}
		
		while(!all.isEmpty()){
			curr = all.peekFirst(); 
			double[] weight = findWeight(curr, ccs);
			if(weight!=null){
				double highest=Double.MIN_VALUE;
				for (V p : all) {
					if(CentralStatics.innerProduct(weight, p.getValue())>highest){
						curr = p;
						highest = CentralStatics.innerProduct(weight, p.getValue());
					}
		        }
				ccs.add(curr);
				all.removeFirstOccurrence(curr);
			} else {
				all.removeFirst();
			}
		}
		
		return ccs;
	}
	
	public double[] findWeight(V p, ArrayList<V> ccs){
		double[] result = null;
		int dimensionality = p.length();
		try {
			LpSolve solver = LpSolve.makeLp(0, dimensionality+1);
			solver.setVerbose(1);
			for(int i=0; i<ccs.size();i++){
				double[] constraint=new double[dimensionality+2];
				Vector curr = ccs.get(i);
				for(int j=0; j<dimensionality; j++){
					constraint[j+1] = (p.getValue(j)-curr.getValue(j));
				}
				constraint[dimensionality+1]=-1;
				solver.addConstraint(constraint, LpSolve.GE, 0);
			}
			
			double[] wConstraint=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				wConstraint[j+1] = 1;
			}
			wConstraint[dimensionality+1]=0;
			solver.addConstraint(wConstraint, LpSolve.EQ, 1);
			
			double[] objective=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				objective[j+1] = 0;
			}
			objective[dimensionality+1]=-1;
			solver.setObjFn(objective);
			
			//solver.printLp();
			
			solver.solve();
			
			// print solution
			//System.out.println("Value of objective function:" +
			//		solver.getObjective());
			double[] var = solver.getPtrVariables();
			if(solver.getStatus()==0){
				result = new double[dimensionality];
				for (int i = 0; i < var.length-1; i++) {
					result[i]=var[i];
				}
			}
			// delete the problem and free memory
			solver.deleteLp();
			
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	public double findImprovement(V p, ArrayList<V> ccs){
		double result = Double.MIN_VALUE;
		int dimensionality = p.length();
		try {
			LpSolve solver = LpSolve.makeLp(0, dimensionality+1);
			solver.setVerbose(1);
			for(int i=0; i<ccs.size();i++){
				double[] constraint=new double[dimensionality+2];
				Vector curr = ccs.get(i);
				for(int j=0; j<dimensionality; j++){
					constraint[j+1] = (p.getValue(j)-curr.getValue(j));
				}
				constraint[dimensionality+1]=-1;
				solver.addConstraint(constraint, LpSolve.GE, 0);
			}
			
			double[] wConstraint=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				wConstraint[j+1] = 1;
			}
			wConstraint[dimensionality+1]=0;
			solver.addConstraint(wConstraint, LpSolve.EQ, 1);
			
			double[] objective=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				objective[j+1] = 0;
			}
			objective[dimensionality+1]=-1;
			solver.setObjFn(objective);
			
			//solver.printLp();
			
			solver.solve();
			
			if(solver.getStatus()==0){
				result = solver.getObjective()*(-1);
			}
			// delete the problem and free memory
			solver.deleteLp();
			
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	public Tuple<Double,double[]> findImprovementAndWeight(V p, ArrayList<V> ccs){
		double result = Double.MIN_VALUE;
		int dimensionality = p.length();
		double[] res = new double[dimensionality]; 
		try {
			LpSolve solver = LpSolve.makeLp(0, dimensionality+1);
			solver.setVerbose(1);
			for(int i=0; i<ccs.size();i++){
				double[] constraint=new double[dimensionality+2];
				Vector curr = ccs.get(i);
				for(int j=0; j<dimensionality; j++){
					constraint[j+1] = (p.getValue(j)-curr.getValue(j));
				}
				constraint[dimensionality+1]=-1;
				solver.addConstraint(constraint, LpSolve.GE, 0);
			}
			
			double[] wConstraint=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				wConstraint[j+1] = 1;
			}
			wConstraint[dimensionality+1]=0;
			solver.addConstraint(wConstraint, LpSolve.EQ, 1);
			
			double[] objective=new double[dimensionality+2];
			for(int j=0; j<=dimensionality; j++){
				objective[j+1] = 0;
			}
			objective[dimensionality+1]=-1;
			solver.setObjFn(objective);
			
			//solver.printLp();
			
			solver.solve();
			
			
			if(solver.getStatus()==0){
				double[] var = solver.getPtrVariables();
				for (int i = 0; i < var.length-1; i++) {
					res[i]=var[i];
				}
				//double val = p.linearScalValue(var);
				result = solver.getObjective()*(-1);
			}
			// delete the problem and free memory
			solver.deleteLp();
			
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
		
		return new Tuple<Double,double[]>(result,res);
	}

	
	//just a silly method to test whether lpsolve works;
	public void testLP(){
		try {
				// Create a problem with 4 variables and 0 constraints
				LpSolve solver = LpSolve.makeLp(0, 4);
				solver.setVerbose(1);
				// add constraints
				solver.strAddConstraint("3 2 2 1", LpSolve.LE, 4);
				solver.strAddConstraint("0 4 3 1", LpSolve.GE, 3);
				//LpSolve.EQ
				// set objective function
				solver.strSetObjFn("2 3 -2 3");
				// solve the problem
				solver.solve();
				// print solution
				System.out.println("Value of objective function:" +
						solver.getObjective());
				double[] var = solver.getPtrVariables();
				for (int i = 0; i < var.length; i++) {
					System.out.println("Value of var[" + i + "] = " + var[i]);
				}
				solver.printLp();
				// delete the problem and free memory
				solver.deleteLp();
		}
		catch (LpSolveException e) {
			e.printStackTrace(); 
		}
	}
	
	//Test whether findWeight works, the right answer is (0.375, 0.625)
	public void tstLP(){		
		SimpleVector<Boolean> p = new SimpleVector<Boolean>(2);
		p.setValue(0,4.2);
		p.setValue(1,4.2);
		
		SimpleVector<Boolean> p2 = new SimpleVector<Boolean>(2);
		p2.setValue(0,5);
		p2.setValue(1,2);
		SimpleVector<Boolean> p3 = new SimpleVector<Boolean>(2);
		//p3.value[0]=0;
		p3.setValue(1,5);
		ArrayList<SimpleVector<Boolean>> uU = new ArrayList<SimpleVector<Boolean>>(2);
		uU.add(p2); uU.add(p3);
		
		CPruner<SimpleVector<Boolean>> cpsv = new CPruner<SimpleVector<Boolean>>();
		double[] bla = cpsv.findWeight(p, uU);
		if(bla==null){
			System.out.println("No weight where entered weight vector is optimal.");
			return;
		}
		for(int j=0; j<2; j++){
			System.out.println(bla[j]);
		}
		double bla2 = cpsv.findImprovement(p, uU);
		System.out.println("Improvement: "+bla2);
	}
}
